<?php

/**
 * Created by PhpStorm.
 * User: heqian
 * Date: 17-4-21
 * Time: 下午9:43
 */
namespace Swoole;

use \swoole_process;
use \swoole_server;

class App
{
    static $daemonize = false;
    static $pidFile = '';
    static $_startFile = '';
    static $logFile = '';
    static $_globalStatistics = '';
    static $_statisticsFile = '';
    static $_status = '';
    static $name = "none";
    static $user = 'root';
    static $master_pid = '';
    static $count = 4;
    static $manage_pid = '';
    static $serv = '';
    static $stats = [];
    const STATUS_STARTING = 1;
    /**
     * Maximum length of the worker names.
     *
     * @var int
     */
    protected static $_maxWorkerNameLength = 12;

    /**
     * Maximum length of the socket names.
     *
     * @var int
     */
    protected static $_maxSocketNameLength = 12;

    /**
     * Maximum length of the process user names.
     *
     * @var int
     */
    protected static $_maxUserNameLength = 12;


    public function __construct($ip = '0.0.0.0', $port = 9501, $set = array())
    {
        $this->init();
        $this->parseCommand();
        $set['dispatch_mode'] = 5;
        $set['heartbeat_check_interval'] = 25;
        $set['heartbeat_idle_time'] = 60;
        $this->http_server = new \swoole_websocket_server($ip, $port);
        $this->set['log_file'] = self::$logFile;
        $this->set['pid_file'] = self::$pidFile;
        $this->set['daemonize'] = self::$daemonize;
        $this->set['worker_num'] = self::$count;
        if (empty($set)) {
            $set["enable_reuse_port"] = true;
        }
        $this->set = array_merge($this->set, $set);
        $this->http_server->set($this->set);
        $this->http_server->on('start', array($this, 'onMasterStart'));
        $this->http_server->on('managerstart', array($this, 'onManagerStart'));
        $this->http_server->on('workerstart', array($this, 'onWorkerStart'));
        $this->http_server->on('shutdown', array($this, 'onShutDown'));
        self::displayUI();

    }

    /**
     * Init.
     *
     * @return void
     */
    protected static function init()
    {
        // Start file.
        $backtrace = debug_backtrace();
        self::$_startFile = $backtrace[count($backtrace) - 1]['file'];

        // Pid file.
        if (empty(self::$pidFile)) {
            self::$pidFile = __DIR__ . "/" . str_replace('/', '_', self::$_startFile) . ".pid";
        }

        // Log file.
        if (empty(self::$logFile)) {
            self::$logFile = __DIR__ . '/swoole.log';
        }
        $log_file = (string)self::$logFile;
        if (!is_file($log_file)) {
            touch($log_file);
            chmod($log_file, 0622);
        }

        // State.
        self::$_status = self::STATUS_STARTING;

        // For statistics.
        self::$_statisticsFile = sys_get_temp_dir() . '/swoole.status';
    }

    protected static function parseCommand()
    {
        global $argv;
        // Check argv;
        $start_file = $argv[0];
        self::$_startFile = $start_file;
        if (!isset($argv[1])) {
            exit("Usage: php yourfile.php {start|stop|restart|reload|status}\n");
        }

        // Get command.
        $command = trim($argv[1]);
        $command2 = isset($argv[2]) ? $argv[2] : '';

        // Start command.
        $mode = '';
        if ($command === 'start') {
            if ($command2 === '-d' || self::$daemonize) {
                $mode = 'in DAEMON mode';
            } else {
                $mode = 'in DEBUG mode';
            }
        }
        self::log("Swoole[$start_file] $command $mode");

        // Get master process PID.
        $master_pid = @file_get_contents(self::$pidFile);
        $pid_arr = explode("\n", $master_pid);
        sort($pid_arr);
        self::$master_pid = isset($pid_arr[0]) ? $pid_arr[0] : 0;
        self::$manage_pid = isset($pid_arr[1]) ? $pid_arr[1] : 0;
        $master_is_alive = self::$master_pid && @swoole_process::kill(self::$master_pid, 0);
        // Master is still alive?
        if ($master_is_alive) {
            if ($command === 'start' && posix_getpid() != self::$master_pid) {
                self::log("Swoole[$start_file] already running");
                exit;
            }
        } elseif ($command !== 'start' && $command !== 'restart') {
            self::log("Swoole[$start_file] not run");
            exit;
        }

        // execute command.
        switch ($command) {
            case 'start':
                if ($command2 === '-d') {
                    self::$daemonize = true;
                }
                break;
            case 'status':
                if (is_file(self::$_statisticsFile)) {
                    @unlink(self::$_statisticsFile);
                }
                // Master process will send status signal to allb child processes.

                foreach (self::getAllWorkerPids() as $worker_pid) {
                    swoole_process::kill($worker_pid, SIGUSR2);
                    break;
                }
                //swoole_process::kill(self::$master_pid, SIGUSR1);
                //echo self::$_statisticsFile;
                // Waiting amoment.
                usleep(500000);
                // Display statisitcs data from a disk file.
                @readfile(self::$_statisticsFile);
                exit(0);
            case 'restart':
            case 'stop':
                self::log("Swoole[$start_file] is stoping ...");
                // Send stop signal to master process.
                self::$master_pid && swoole_process::kill(self::$master_pid, SIGTERM);
                // Timeout.
                $timeout = 5;
                $start_time = time();
                // Check master process is still alive?
                while (1) {
                    $master_is_alive = self::$master_pid && swoole_process::kill(self::$master_pid, 0);
                    if ($master_is_alive) {
                        // Timeout?
                        if (time() - $start_time >= $timeout) {
                            self::log("Swoole[$start_file] stop fail");
                            exit;
                        }
                        // Waiting amoment.
                        usleep(10000);
                        continue;
                    }
                    // Stop success.
                    self::log("Swoole[$start_file] stop success");
                    if ($command === 'stop') {
                        exit(0);
                    }
                    if ($command2 === '-d') {
                        self::$daemonize = true;
                    }
                    break;
                }
                break;
            case 'reload':
                swoole_process::kill(self::$master_pid, SIGUSR1);
                self::log("Swoole[$start_file] reload");
                exit;
            default :
                exit("Usage: php yourfile.php {start|stop|restart|reload|status}\n");
        }
    }

    /**
     * Install signal handler.
     *
     * @return void
     */
    protected static function installSignal()
    {
        // status
        swoole_process::signal(SIGUSR2, array('\Swoole\App', 'writeStatisticsToStatusFile'));
    }

    /**
     * Log.
     *
     * @param string $msg
     * @return void
     */
    public static function log($msg)
    {
        $msg = $msg . "\n";
        if (!self::$daemonize) {
            self::safeEcho($msg);
        }
        file_put_contents((string)self::$logFile, date('Y-m-d H:i:s') . ' ' . 'pid:' . self::$master_pid . ' ' . $msg, FILE_APPEND | LOCK_EX);
    }

    /**
     * Display staring UI.
     *
     * @return void
     */
    protected static function displayUI()
    {
        self::safeEcho("\033[1A\n\033[K-----------------------\033[47;30m Swoole \033[0m-----------------------------\n\033[0m");
        self::safeEcho('Swoole version:' . swoole_version() . "          PHP version:" . PHP_VERSION . "\n");
        self::safeEcho("------------------------\033[47;30m WORKERS \033[0m-------------------------------\n");
        self::safeEcho("\033[47;30muser\033[0m" . str_pad('',
                self::$_maxUserNameLength + 2 - strlen('user')) . "\033[47;30mworker\033[0m" . str_pad('',
                self::$_maxWorkerNameLength + 2 - strlen('worker')) . "\033[47;30mlisten\033[0m" . str_pad('',
                self::$_maxSocketNameLength + 2 - strlen('listen')) . "\033[47;30mprocesses\033[0m \033[47;30m" . "status\033[0m\n");

        //foreach (self::$_workers as $worker) {
        self::safeEcho(str_pad(self::$user, self::$_maxUserNameLength + 2) . str_pad(self::$name,
                self::$_maxWorkerNameLength + 2) . str_pad(self::$name,
                self::$_maxSocketNameLength + 2) . str_pad(' ' . self::$count, 9) . " \033[32;40m [OK] \033[0m\n");
        //}
        self::safeEcho("----------------------------------------------------------------\n");
        if (self::$daemonize) {
            global $argv;
            $start_file = $argv[0];
            self::safeEcho("Input \"php $start_file stop\" to quit. Start success.\n");
        } else {
            self::safeEcho("Press Ctrl-C to quit. Start success.\n");
        }
    }

    /**
     * Safe Echo.
     *
     * @param $msg
     */
    public static function safeEcho($msg)
    {
        if (!function_exists('posix_isatty') || posix_isatty(STDOUT)) {
            echo $msg;
        }
    }

    public function run()
    {
        $this->http_server->start();
    }

    public function onMasterStart($serv)
    {
        //$this->installSignal();
        self::$serv = $serv;
        self::$master_pid = $serv->master_pid;
        swoole_set_process_name("Swoole: master process " . self::$name . " start_file=" . self::$_startFile);
    }

    public function onManagerStart($serv)
    {
        self::$serv = $serv;
        if (false === @file_put_contents(self::$pidFile, "\n" . $serv->manager_pid, FILE_APPEND)) {
            throw new Exception('can not save pid to ' . self::$pidFile);
        }
        swoole_set_process_name("Swoole: manage process " . self::$name . " start_file=" . self::$_startFile);
    }

    public function onWorkerError($serv, $worker_id, $worker_pid, $exit_code)
    {
        $this->log("worker_pid: " . $worker_pid . " exit_code:" . $exit_code);
    }

    public function onWorkerStart($serv)
    {
        $this->installSignal();
        self::$serv = $serv;
        if (false === @file_put_contents(self::$pidFile, "\n" . $serv->worker_pid, FILE_APPEND)) {
            throw new Exception('can not save pid to ' . self::$pidFile);
        }
        swoole_set_process_name("Swoole: worker process " . self::$name . " start_file=" . self::$_startFile);
    }

    public function onShutDown()
    {
        if (false === @unlink(self::$pidFile)) {
            throw new Exception('can not remove ' . self::$pidFile);
        }
    }

    /**
     * Write statistics data to disk.
     *
     * @return void
     */
    public static function writeStatisticsToStatusFile()
    {
        // For master process.
        //var_dump(self::$serv);
        self::$stats = self::$serv->stats();
        var_dump(self::$serv->worker_id);
        var_dump(self::$serv->worker_pid);
        if (!is_file(self::$_statisticsFile)) {
            $loadavg = function_exists('sys_getloadavg') ? sys_getloadavg() : array('-', '-', '-');
            file_put_contents(self::$_statisticsFile,
                "---------------------------------------GLOBAL STATUS--------------------------------------------\n");
            file_put_contents(self::$_statisticsFile,
                'Swoole version:' . swoole_version() . "             PHP version:" . PHP_VERSION . "\n", FILE_APPEND);
            file_put_contents(self::$_statisticsFile, 'start time:' . date('Y-m-d H:i:s',
                    self::$stats['start_time']) . '   run ' . floor((time() - self::$stats['start_time']) / (24 * 60 * 60)) . ' days ' . floor(((time() - self::$stats['start_time']) % (24 * 60 * 60)) / (60 * 60)) . " hours   \n",
                FILE_APPEND);
            $load_str = 'load average: ' . implode(", ", $loadavg);
            file_put_contents(self::$_statisticsFile,
                str_pad($load_str, 33) . 'event-loop:Swoole' . "\n", FILE_APPEND);
            file_put_contents(self::$_statisticsFile,
                self::$count . ' workers       ' . self::$count . " processes\n",
                FILE_APPEND);
            file_put_contents(self::$_statisticsFile,
                str_pad('worker_name', self::$_maxWorkerNameLength) . " connection_num  accept_count    tasking_num     close_count    request_count\n", FILE_APPEND);

            file_put_contents(self::$_statisticsFile,
                str_pad(self::$name, self::$_maxWorkerNameLength) . " " . str_pad(self::$stats['connection_num'],
                    16) . str_pad(self::$stats['accept_count'],
                    16) . str_pad(self::$stats['tasking_num'],
                    16) . str_pad(self::$stats['close_count'], 16) . self::$stats['request_count'] . "\n", FILE_APPEND);
            file_put_contents(self::$_statisticsFile,
                "---------------------------------------PROCESS STATUS-------------------------------------------\n",
                FILE_APPEND);
            file_put_contents(self::$_statisticsFile,
                "pid\tmemory  " . str_pad('listening', self::$_maxSocketNameLength) . " " . str_pad('worker_name',
                    self::$_maxWorkerNameLength) . " connections " . str_pad('total_request',
                    13) . " " . str_pad('send_fail', 9) . " " . str_pad('throw_exception', 15) . "\n", FILE_APPEND);
            self::childWrite();
            chmod(self::$_statisticsFile, 0722);
            $i = 0;
            foreach (self::getAllWorkerPids() as $worker_pid) {
                $i++;
                if ($i == 1) {
                    continue;
                }
                swoole_process::kill($worker_pid, SIGUSR2);
            }
        } else if (self::$serv->worker_pid === posix_getpid()) {
            self::childWrite();
        }
    }

    /**
     * Get all pids of worker processes.
     *
     * @return array
     */
    protected static function getAllWorkerPids()
    {
        $pids = @file_get_contents(self::$pidFile);
        $pidarr = explode("\n", $pids);
        sort($pidarr);
        unset($pidarr[0]);
        unset($pidarr[1]);
        //var_dump($pidarr);
        return $pidarr;
    }

    public static function childWrite()
    {

        // For child processes.
        /** @var Worker $worker */
        $worker_status_str = posix_getpid() . "\t" . str_pad(round(memory_get_usage(true) / (1024 * 1024), 2) . "M",
                7) . " " . str_pad(self::$name,
                self::$_maxSocketNameLength) . " " . str_pad((self::$name === self::$name ? 'none' : self::$name),
                self::$_maxWorkerNameLength) . " ";
        $worker_status_str .= str_pad(self::$stats['worker_request_count'],
                11) . " " . str_pad(self::$stats['request_count'],
                14) . " " . str_pad(self::$stats['worker_request_count'],
                9) . " " . str_pad(self::$stats['request_count'], 15) . "\n";
        file_put_contents(self::$_statisticsFile, $worker_status_str, FILE_APPEND);
    }

}